package scheduler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import model.Entidade;
import model.LockInfo;
import model.Operacao;
import utils.Granularidade;
import utils.Utils;

public class Scheduler {
	
	private static HashMap<String,Entidade> banco;
	private static ArrayList<LockInfo> bloqueios;
	private static ArrayList<Operacao> esperando;
	
	public static ArrayList<Operacao> start(String schedule){
		Entidade database = Utils.initializeDB();
		banco = Utils.initializeDBtoMap();
		ArrayList<Operacao> entrada = Utils.getListaOperacoes(schedule);
		ArrayList<Operacao> escalonados = new ArrayList<Operacao>();
		esperando = new ArrayList<Operacao>();
		bloqueios = new ArrayList<LockInfo>();
		Operacao inConflict = null;
		for (Operacao operacao : entrada) {
			if(operacao.toString().equalsIgnoreCase("C2()")){
				System.out.println();
			}
			//adiciona a primeira operacao a lista de escalonado e a tabela de bloqueios
			if (escalonados.size() == 0){
				escalonados.add(operacao);
				bloqueios.add(new LockInfo(operacao.getTrId(), -1, operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.GRANTED));
				//add intencionais
				addBloqueiosIntencionais(operacao, getLockType(operacao.getAcao().toUpperCase().charAt(0)));
			} else if(operacao.getAcao().equalsIgnoreCase("C")){
				//Se for um commit
				if(isTransactionInWaiting(operacao, esperando)){
					//se tiver coloca a operacao em espera junto das demais
					bloqueios.add(new LockInfo(operacao.getTrId(), -1, operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.WAITING));
					esperando.add(operacao);
				}else{
					liberarBloqueios(operacao);
					liberarBloqueiosIntencionais(operacao);
					escalonados.add(operacao);
					escalonarOperacoesEmEspera(escalonados, operacao);
					
				}
			} else {
				inConflict = isInConflict(escalonados, operacao);
				//verifica se a transacao possui alguma operacao em espera
				if(isTransactionInWaiting(operacao, esperando)){
					//se tiver coloca a operacao em espera junto das demais
					bloqueios.add(new LockInfo(operacao.getTrId(), -1, operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.WAITING));
					esperando.add(operacao);
				}else if(inConflict == null) {
					//se nao estiver em conflito a operacao e escalonada
					boolean isLockCompatible = vefiricaCompatibilidadeBloqueioGranularidade(operacao);
					if(isLockCompatible){
						bloqueios.add(new LockInfo(operacao.getTrId(), -1, operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.GRANTED));
						addBloqueiosIntencionais(operacao, getLockType(operacao.getAcao().toUpperCase().charAt(0)));
						escalonados.add(operacao);
					}
				}else{
					bloqueios.add(new LockInfo(operacao.getTrId(), inConflict.getTrId(), operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.WAITING));
					esperando.add(operacao);
				}
			}
			inConflict = null;
		}
		return escalonados;
	}

	private static void escalonarOperacoesEmEspera(ArrayList<Operacao> escalonados, Operacao operacao) {
		ArrayList<LockInfo> waitingLocks = new ArrayList<LockInfo>();
		for (LockInfo lockInfo : bloqueios) {
			if(operacao.getTrId() == lockInfo.getTrIdNext() && lockInfo.getLockType() == LockInfo.WAITING){
				waitingLocks.add(lockInfo);
			}
		}
		ArrayList<Operacao> esperandoAux = new ArrayList<Operacao>();
		for (LockInfo lockInfo : waitingLocks) {
			for (Operacao op : esperando) {
				if(lockInfo.getTrId() == op.getTrId()){
					if (consegueEscalonarOperacao(escalonados, op, operacao)){
						esperandoAux.add(op);
						escalonados.add(op);
					}
				}
			}
			esperando.removeAll(esperandoAux);
		}
		
	}

	private static boolean consegueEscalonarOperacao(ArrayList<Operacao> escalonados, Operacao op, Operacao operacao) {
		boolean consegue = false;
		Operacao inConflict = isInConflict(escalonados, op, operacao);
		if(inConflict != null){
			bloqueios.add(new LockInfo(op.getTrId(), inConflict.getTrId(), op.getObjId(), getLockType(op.getAcao().charAt(0)), LockInfo.WAITING));
		}else{
			bloqueios.add(new LockInfo(op.getTrId(), -1, op.getObjId(), getLockType(op.getAcao().charAt(0)), LockInfo.GRANTED));
			if(LockInfo.deadLockCheck(bloqueios)){
				//TODO ABORTAR TRANSACAO DE OP
			}
			consegue = true;
		}
		return consegue;
	}

	private static void liberarBloqueiosIntencionais(Operacao operacao) {
		Iterator<Entry<String, Entidade>> ite = banco.entrySet().iterator();
		while(ite.hasNext()){
			Entry<String, Entidade> entry = ite.next();
			Entidade e = entry.getValue();
			ArrayList<LockInfo> removidos = new ArrayList<LockInfo>();
			for (LockInfo lockInfo : e.getIntencionais()) {
				if(lockInfo.getTrId() == operacao.getTrId()){
					removidos.add(lockInfo);
				}
			}
			e.getIntencionais().removeAll(removidos);
		}
	}

	private static void liberarBloqueios(Operacao operacao) {
		ArrayList<LockInfo> removidos = new ArrayList<LockInfo>();
		for (LockInfo lockInfo : bloqueios) {
			if(lockInfo.getTrId() == operacao.getTrId()){
				removidos.add(lockInfo);
			}
		}
		bloqueios.removeAll(removidos);
	}

	private static boolean vefiricaCompatibilidadeBloqueioGranularidade(Operacao operacao) {
		Entidade entidadeOP = banco.get(operacao.getObjId());
		return verificarBloqueios(entidadeOP.getPai(), operacao);
		
	}
	
	private static boolean verificarBloqueios(Entidade pai, Operacao operacao) {
		if(pai != null){
			boolean retorno = true;
			for (LockInfo lockInfo : pai.getIntencionais()) {
				if(lockInfo.getTrId() != operacao.getTrId()){
					retorno = Granularidade.isLockCompatible(lockInfo.getLockType(), getLockType(operacao.getAcao().charAt(0)));
				}
				if(!retorno){
					bloqueios.add(new LockInfo(operacao.getTrId(), lockInfo.getTrId(), operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.WAITING));
					if(!LockInfo.deadLockCheck(bloqueios)){
						//TODO
					}
					esperando.add(operacao);
				}
			}
			return retorno && verificarBloqueios(pai.getPai(), operacao);
			
		}
		return true;
	}

	private static Operacao isInConflict(ArrayList<Operacao> escalonados, Operacao operacao) {
		return isInConflict(escalonados, operacao, null);
	}
	
	private static Operacao isInConflict(ArrayList<Operacao> escalonados, Operacao operacao, Operacao operacaoAtual) {
		Operacao inConflict = null;
		//percorre a lista de operacoes escalonadas
		for (Operacao escalonado : escalonados) {
			//se as transacoes ocorrem sobre o mesmo objeto
			if (escalonado.getObjId().equalsIgnoreCase(operacao.getObjId())) {
				//verifica se sao transacoes de ID diferentes
				if (escalonado.getTrId() != operacao.getTrId()) {
					//verifica se pelo menos uma operacao e de escrita
					if(escalonado.getAcao().equalsIgnoreCase("W") | operacao.getAcao().equalsIgnoreCase("W")){
						if(operacaoAtual != null && !operacaoAtual.getAcao().equalsIgnoreCase("C") && escalonado.getTrId() != operacaoAtual.getTrId()){
							inConflict = escalonado;
							System.out.println(inConflict.toString()+" em conflito com "+ operacao.toString());
							bloqueios.add(new LockInfo(operacao.getTrId(), escalonado.getTrId(), operacao.getObjId(), getLockType(operacao.getAcao().toUpperCase().charAt(0)), LockInfo.WAITING));
							if(!LockInfo.deadLockCheck(bloqueios)){
								//TODO
							}
						}
					}
				}
			}
		}
		return inConflict;
	}
	

	private static void addBloqueiosIntencionais(Operacao operacao, int lockType) {
		Entidade e = banco.get(operacao.getObjId());
		int intentLock = -1;
		if(lockType == LockInfo.RL){
			intentLock = LockInfo.IRL;
		}else if(lockType == LockInfo.WL){
			intentLock = LockInfo.IWL;
		}else if(lockType == LockInfo.UL){
			intentLock = LockInfo.IUL;
		}
		addBloqueios(e.getPai(), operacao, intentLock);
		
	}


	private static Entidade addBloqueios(Entidade pai, Operacao operacao, int lockType) {
		if(pai != null){
			addBloqueios(pai.getPai(), operacao, lockType);
			pai.getIntencionais().add(new LockInfo(operacao.getTrId(), -1, pai.getName(), lockType, -1));
		}
		return null;
	}


	private static boolean isTransactionInWaiting(Operacao operacao, ArrayList<Operacao> esperando) {
		boolean isWaiting = false;
		for (Operacao waiting : esperando) {
			if(operacao.getTrId() == waiting.getTrId()){
				isWaiting = true;
				break;
			}
		}
		return isWaiting;
	}

	private static int getLockType(char acao) {
		int lockType = -1;
		switch(acao){
		case 'R':
			lockType = LockInfo.RL;
			break;
		case 'W':
			lockType = LockInfo.WL;
			break;
		case 'U':
			lockType = LockInfo.UL;
			break;
		}
		return lockType;
	}

	public static void printSchedule(ArrayList<Operacao> escalonados) {
		System.out.print("\nS = ");
		for (Operacao op : escalonados) {
			System.out.print(op.toString()+" ");
		}
		
	}
}
